#include "StdAfx.h"
#include "AdditiveFactory.h"

#include <cmath>

using namespace std;


CAdditiveFactory::CAdditiveFactory(void)
{
	mAmps.push_back(1.0);
	mAmps.push_back(0.25);
	mAmps.push_back(0.2);
	mAmps.push_back(0.1);
	mAmps.push_back(0.05);
	mAmps.push_back(0.025);
	mAmps.push_back(0.005);
	mAmps.push_back(0.001);
}


CAdditiveFactory::~CAdditiveFactory(void)
{
}

CAdditiveInstrument *CAdditiveFactory::CreateInstrument()
{
	// Instantiate
	CAdditiveInstrument *instrument = new CAdditiveInstrument();

	// Set instrument to play wave from wavetable
	instrument->GetSynth()->SetSamples(&mWaveTable[mNote][0], mWaveTable[mNote].size());

	return instrument;
}

double *CAdditiveFactory::GetWave(wstring note)
{
	if (mWaveTable.find(note) == mWaveTable.end())
		GenWave(note);

	return &mWaveTable[note][0];
}

void CAdditiveFactory::GenWave(wstring note)
{
	vector<double> wave; // Current note's waveform

	// For each harmonic
	int numHarmonics = ( mAmps.size() >= 8 ) ? mAmps.size() : 8; // Minimum 8 harmonics
	for (int harmonic = 0; harmonic < numHarmonics; harmonic++)
	{
		// Generate a sine wave at the proper frequency with the given amplitude
		double freq = (1+harmonic) * NoteToFrequency(note.c_str()); // Frequency of a harmonic is h * fundamentalFreq
		double amp = ( harmonic < int(mAmps.size()) ) ? mAmps[harmonic] : 1.0; // If not defined, amp is 0

		mSineWave.Start();
		mSineWave.SetFreq( freq );
		mSineWave.SetAmplitude( amp );

		// Is the harmonic within the Nyquist frequency?
		if (freq <= (GetSampleRate() / 2.0))
		{
			if (harmonic == 0)
			{
				bool pastHalf = false;
				double sample = 0.0;

				while (mSineWave.Generate())
				{
					double frame = mSineWave.Frame(0);
					if ( mSineWave.Frame(0) > 0.0 && pastHalf )
					{
						// One full wavelength, we're done
						break;
					}
					else if ( mSineWave.Frame(0) < 0.0 )
					{
						// Over half a wavelength, flag it
						pastHalf = true;
					}

					// Populate the wave
					wave.push_back( mSineWave.Frame(0) );
				}
			}
			else
			{
				// For each sample in the wave
				for (int sample = 0; sample < int(wave.size()); sample++)
				{
					// Generate the harmonic's sample
					mSineWave.Generate();

					// Add the harmonic's sample to the current wave's sample
					wave[sample] += mSineWave.Frame(0);
				}
			}
		}
	}

	// Push wave back to wavetable
	mWaveTable.insert(pair<wstring, vector<double> >(note, wave));
}

void CAdditiveFactory::SetNote(CNote *aNote)
{
	// Get a list of all attribute nodes and the
    // length of that list
    CComPtr<IXMLDOMNamedNodeMap> attributes;
    aNote->Node()->get_attributes(&attributes);
    long len;
    attributes->get_length(&len);

    // Loop over the list of attributes
    for(int i=0;  i<len;  i++)
    {
        // Get attribute i
        CComPtr<IXMLDOMNode> attrib;
        attributes->get_item(i, &attrib);

        // Get the name of the attribute
        CComBSTR name;
        attrib->get_nodeName(&name);

        // Get the value of the attribute.  A CComVariant is a variable
        // that can have any type. It loads the attribute value as a
        // string (UNICODE), but we can then change it to an integer 
        // (VT_I4) or double (VT_R8) using the ChangeType function 
        // and then read its integer or double value from a member variable.
        CComVariant value;
        attrib->get_nodeValue(&value);

        if(name == "note")
        {
			// Set mNote
			mNote = value.bstrVal;

			// Is wave in wavetable?
			if ( mWaveTable.find(mNote) == mWaveTable.end() )
			{
				// If not, generate it
				GenWave( mNote );
			}

        }
    }

}